package com.sskj.gateway.protocol.codec;

import com.sskj.common.constant.RedisConstant;
import com.sskj.common.dto.DeviceTelemetry;
import com.sskj.common.enums.DeviceMessageTypeEnum;
import com.sskj.common.util.StrUtil;
import com.sskj.gateway.conntant.ChannelAttributesConstant;
import com.sskj.gateway.event.DeviceMessageEvent;
import com.sskj.gateway.protocol.ProtocolRegistry;
import com.sskj.protocol.IProtocol;
import com.sskj.protocol.sskjboard.model.FrameMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.nio.charset.StandardCharsets;
import java.time.Instant;

@Slf4j
public class FrameMessageInboundHandler extends SimpleChannelInboundHandler<FrameMessage> {

    private final ProtocolRegistry protocolRegistry;

    private final StringRedisTemplate stringRedisTemplate;

    private final ApplicationEventPublisher eventPublisher;

    public FrameMessageInboundHandler(ProtocolRegistry protocolRegistry, StringRedisTemplate stringRedisTemplate, ApplicationEventPublisher eventPublisher) {
        this.protocolRegistry = protocolRegistry;
        this.stringRedisTemplate = stringRedisTemplate;
        this.eventPublisher = eventPublisher;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FrameMessage frameMessage) throws Exception {
        // 判断是否注册，如果未注册，获取注册信息进行相关绑定工作(设备CODE和连接绑定、设备CODE和协议解析包绑定等等)；如果已成功注册，则获取对应的解析包进行对应操作
        String deviceCode = channelHandlerContext.channel().attr(ChannelAttributesConstant.DEVICE_CODE).get();
        if (StrUtil.isBlank(deviceCode)) {
            if (frameMessage.getCommandType() == 0x01 && frameMessage.getCommandCode() == 0x02) {
                byte[] codeBytes = new byte[32];
                System.arraycopy(frameMessage.getData(), 32, codeBytes, 0,32);
                deviceCode = trimString(new String(codeBytes, StandardCharsets.UTF_8));

                channelHandlerContext.channel().attr(ChannelAttributesConstant.DEVICE_CODE).set(deviceCode);
            }
        }

        if (StrUtil.isNotBlank(deviceCode)) {
            // 获取设备CODE绑定的通信协议CODE，获取到指定的通信协议包实例
            String deviceProtocolKey = RedisConstant.DEVICE_PROTOCOL_MAPPING + deviceCode;
            String deviceProtocolCode = stringRedisTemplate.opsForValue().get(deviceProtocolKey);
            if (StrUtil.isBlank(deviceProtocolCode)) {
                log.warn("设备 {} 未绑定任何通信协议", deviceCode);
                return;
            }

            // 获取设备绑定对应的通信协议实例
            IProtocol protocol = protocolRegistry.getProtocol(deviceProtocolCode);

            if (protocol == null) {
                log.warn("通信协议 {} 未注册", deviceProtocolCode);
                return;
            }

            try {
                DeviceTelemetry deviceTelemetry = protocol.decode(frameMessage.buildFrame());
                deviceTelemetry.setDeviceCode(deviceCode);

                // 发布统一事件
                eventPublisher.publishEvent(new DeviceMessageEvent(deviceTelemetry, channelHandlerContext));
            } catch (Exception e) {
                log.error("协议处理失败: {}", deviceCode, e);
            }
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端已断开: {}", ctx.channel().remoteAddress());

        String deviceCode = ctx.channel().attr(ChannelAttributesConstant.DEVICE_CODE).get();
        DeviceTelemetry deviceTelemetry = new DeviceTelemetry();
        deviceTelemetry.setDeviceCode(deviceCode);
        deviceTelemetry.setType(DeviceMessageTypeEnum.OFFLINE.getType());
        deviceTelemetry.setTimestamp(Instant.now().toEpochMilli());

        // 发布统一事件
        eventPublisher.publishEvent(new DeviceMessageEvent(deviceTelemetry, ctx));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("连接异常", cause);
        ctx.close();
    }

    private String trimString(String str) {
        return str.replaceAll("\0", "").trim();
    }
}
